import fg from 'fast-glob'

import { PagePath } from '../webpack/cypress-constants'

import { log } from './utils'

interface E2ETaskItem {
  name?: string
  pagePaths: PagePath[]
  testFiles: string[]
}

interface E2ETasks {
  [taskId: string]: E2ETaskItem
}
interface E2ETaskConfig {
  /** 应该覆盖所有测试用例的路径集合（只为校验） */
  include: string[]
  /**
   * 所有应该剔除的测试用例的路径集合，
   * 当 includes - 所有 task 的 testFiles - excludes 不为空集时，e2e 脚本将不通过
   * 该字段为了校验与跳过测试文件，优先度 excludes > text files > includes
   */
  exclude: string[]
}

/** 粗略整理一下页面和测试文件（精确到子文件夹）的对应关系 */
type _GroupsKey =
  | 'apmplus-web'
  | 'base'
  | 'heatmap'
  | 'integrations'
  | 'rangers-site-sdk'
  | 'sdk-hybrid'
  | 'sdk-init-config-script'
  | 'sdk-multi-instance'
  | 'sdk-npm'
  | 'sdk-script'
  | 'sdk-script-sri'
  | 'slardar-lite'
  | 'slardar-react'
  | 'web'
// TODO: 当在 integration 下新建文件夹时，需要在这里补充新的键值对
const _groups: Record<_GroupsKey, E2ETaskItem> = {
  'apmplus-web': {
    pagePaths: [
      'apmplus-web.html',
      'apmplus-web-multi-instances.html',
      'apmplus-web-npm.html',
      'apmplus-web-precollect.html',
      'sdk-npm.html',
    ],
    testFiles: ['cypress/integration/apmplus-web/**/*.spec.ts'],
  },
  base: {
    pagePaths: ['base.html'],
    testFiles: ['cypress/integration/base/**/*.spec.ts'],
  },
  heatmap: {
    pagePaths: ['sdk-inline.html'],
    testFiles: ['cypress/integration/heatmap/**/*.spec.ts'],
  },
  // TODO：这里的任务太杂了 需看数据分析是否需要打散
  integrations: {
    pagePaths: ['apmplus-web-precollect.html', 'apmplus-web.html', 'base.html', 'web.html', 'web-precollect.html', 'web-minimal.html'],
    testFiles: ['cypress/integration/integrations/**/*.spec.ts'],
  },
  'rangers-site-sdk': {
    pagePaths: ['rangers-site-sdk.html', 'rangers-site-sdk-precollect.html'],
    testFiles: ['cypress/integration/rangers-site-sdk/**/*.spec.ts'],
  },
  'sdk-hybrid': {
    pagePaths: ['sdk-hybrid.html'],
    testFiles: ['cypress/integration/sdk-hybrid/**/*.spec.ts'],
  },
  'sdk-init-config-script': {
    pagePaths: ['sdk-init-config-script.html'],
    testFiles: ['cypress/integration/sdk-init-config-script/**/*.spec.ts'],
  },
  'sdk-multi-instance': {
    pagePaths: ['sdk-multi-instance.html'],
    testFiles: ['cypress/integration/sdk-multi-instance/**/*.spec.ts'],
  },
  'sdk-npm': {
    pagePaths: ['sdk-npm.html'],
    testFiles: ['cypress/integration/sdk-npm/**/*.spec.ts'],
  },
  'sdk-script': {
    pagePaths: ['sdk-inline.html', 'sdk-script-base.html'],
    testFiles: ['cypress/integration/sdk-script/**/*.spec.ts'],
  },
  'sdk-script-sri': {
    pagePaths: ['sdk-script-sri.html'],
    testFiles: ['cypress/integration/sdk-script-sri/**/*.spec.ts'],
  },
  'slardar-lite': {
    pagePaths: ['slardar-lite.html'],
    testFiles: ['cypress/integration/slardar-lite/**/*.spec.ts'],
  },
  'slardar-react': {
    pagePaths: ['web.html'],
    testFiles: ['cypress/integration/slardar-react/**/*.spec.ts'],
  },
  web: {
    pagePaths: [
      'web.html',
      'web-multi-instances.html',
      'web-npm.html',
      'web-multi-precollect.html',
      'web-precollect.html',
      'web-multiBidSender.html',
    ],
    testFiles: ['cypress/integration/web/**/*.spec.ts'],
  },
}

/** 配置项 */
const e2eConfig: E2ETaskConfig = {
  include: ['cypress/integration/**/*.spec.ts'],
  exclude: ['cypress/integration/sdk-hybrid/**/*.spec.ts'],
}

/** 组合一下 最后组合的数量等同于并行数, task id 命名规则为 [A-Za-z_] */
const { mergeTaskGroups, hasMerged } = createMergeManager()
export const taskGroups: E2ETasks = filterTestFiles(
  {
    aw_i: mergeTaskGroups(['apmplus-web', 'integrations'], _groups, 'apmplus-web+integration'),
    web: mergeTaskGroups(['web'], _groups),
    rss_rl: mergeTaskGroups(['rangers-site-sdk', 'slardar-lite'], _groups, 'rangers-site-sdk+slardar-lite'),
    others: mergeTaskGroups(getOthersGroupKeys(_groups, hasMerged), _groups),
  },
  e2eConfig,
)

/** 校验分组和配置合法性 */
export function checkE2ETasks(taskGroups: E2ETasks) {
  // 校验所有子任务 id 命名是否合法
  Object.keys(taskGroups).forEach((taskId) => {
    const r = new RegExp('^[A-Za-z_]+$')
    if (!r.test(taskId)) {
      log('error', `${taskId} does not match ${r}`)
      return false
    }
  })
  // 检查是否有单测没有被手动分组
  const allTestFiles = Object.values(taskGroups).reduce((res, task) => [...res, ...task.testFiles], [] as string[])
  const unCoverTestFiles = filterPath(e2eConfig.include, [...new Set([...e2eConfig.exclude, ...allTestFiles])])
  if (unCoverTestFiles.length !== 0) {
    log('error', `You do not group TestFiles ${unCoverTestFiles.join(',')}`)
    return false
  }
  log('success', 'Check e2e tasks completely')
  return true
}

/** 过滤子任务中应该被排除的文件 */
function filterTestFiles(taskGroups: E2ETasks, config: E2ETaskConfig): E2ETasks {
  return Object.entries(taskGroups).reduce((res, [taskId, task]) => {
    const newTask: E2ETaskItem = {
      name: task.name,
      pagePaths: [...task.pagePaths],
      testFiles: filterPath(task.testFiles, config.exclude),
    }
    res[taskId] = newTask
    return res
  }, {} as E2ETasks)
}

/** 自动生成 others 分组，即未手动分组的子任务将合成一组 */
function getOthersGroupKeys(groups: typeof _groups, hasMerged: _GroupsKey[]) {
  const othersGroupKeys = Object.keys(groups).filter(
    (taskId) => !hasMerged.find((mergedId) => taskId === mergedId),
  ) as _GroupsKey[]
  log('info', 'auto generate group named "others", include keys', `[${othersGroupKeys.join(', ')}]`)
  return othersGroupKeys
}

/** 合并两个子任务 */
function createMergeManager() {
  /** 保存所有已合并的 taskId 用于后续生成 others 分组 */
  const hasMerged: _GroupsKey[] = []
  function mergeTaskGroups(taskIds: _GroupsKey[], groups: typeof _groups, name?: string): E2ETaskItem {
    hasMerged.push(...taskIds)
    const newPagePaths = taskIds.reduce((res, taskId) => [...res, ...groups[taskId].pagePaths], [] as PagePath[])
    const newTestFiles = taskIds.reduce((res, taskId) => [...res, ...groups[taskId].testFiles], [] as string[])
    return {
      name,
      pagePaths: [...new Set(newPagePaths)],
      testFiles: [...new Set(newTestFiles)],
    }
  }
  return { mergeTaskGroups, hasMerged }
}

function filterPath(includes: string[], excludes: string[]) {
  return fg.sync(includes, { ignore: excludes })
}
